#include "level.h"

Level::Level()
{
    init();
}

Level::Level(Map *map, Player *player)
{
    init();
    this->m_map = map;
    this->m_player = player;
    m_countMonsters = 0;
    m_countFruits = map->countFruits();
}


Level::~Level()
{
    delete this->m_map;
    delete this->m_player;
}


Map *Level::getMap() const {return this->m_map;}
Player *Level::getPlayer() const {return this->m_player;}
QList<Monster*> Level::getMonsters() const {return this->m_monsters;}
QList<Rock*> Level::getRocks() const {return this->m_rocks;}

void Level::setMap(Map *map) {m_map = map; if(map) m_countFruits = map->countFruits();}
void Level::setPlayer(Player *player) {this->m_player = player;}

void Level::addMonster(Monster *monster) {m_monsters.append(monster); m_countMonsters++;}
Monster *Level::getMonsterAt(int pos) {return m_monsters.at(pos);}
void Level::addRock(Rock *rock) {m_rocks.append(rock);}
Rock *Level::getRockAt(int pos) {return m_rocks.at(pos);}


void Level::init()
{
    this->setMap(NULL);
    this->setPlayer(NULL);
    m_countMonsters = 0;
    m_countFruits = 0;
    m_win = false;
}

bool Level::isWin() {return m_win;}

/* 
  PROBLÈMES A RÉSOUDRE POUR LES COLLISIONS
  + le player et les monstres sont par moment téléporté plus loin
    -> peut être du à un cancelmove qui fait repartir plus loin que prévu parce que le dernier pas avait était tronqué
  + les rock qui réapparaisse aprés disparition en bas de carte (des fois en boucle)
    -> lors de boucle de réapparition de deux rock en continue si l'on suppression d'autres rock ils se supprime aussi
  + les monstres qui passent sous un rock à l'arrêt se font tuer
    -> a priori c'est réglé mais à voir.
    -> c'était pas réglé
 */

bool Level::playerCollidesWithMap(Player *player)
{
    Unit *u;
    bool cancelMove = false;
    int left, top, right, bottom;

    if(player->getGeometry().top() < 0 ||
       player->getGeometry().left() < 0 ||
       player->getGeometry().bottom() > (m_map->getHeight()) ||
       player->getGeometry().right() > (m_map->getWidth()))
    {
       player->truncMove(m_map->getWidth() - 1, m_map->getHeight() - 1, 0, 0);
       return true;
    }
    
    foreach(MapItem *item, m_map->getMapItemsContaining(player))
    {
        for(int k = 0; k < item->getNbUnits(); k++)
        {
            u = item->getUnitAt(k);
            if(!u->collidesWith(player)) break;
            switch(u->getType())
            {
                case Unit::Block :
                    u->getGeometry(left, top, right, bottom);
                    if(! m_map->digBlock(item))
                        cancelMove = true;
                    break;
                case Unit::Fruit :
                    m_map->eatFruit(item);
                    m_countFruits--;
                    break;
                case Unit::Door :
                    if ((m_countMonsters == 0) && (m_countFruits == 0))
                        m_win = true;
                    break;
                default : break;
            }
        }
    }

    if(cancelMove)
        player->truncMove(left, top, right, bottom);
    return cancelMove;
}

bool Level::playerCollidesWithMonster(Player *player)
{
    bool cancelMove = false;
    int minLeft = m_map->getWidth(), minTop = m_map->getHeight(), maxRight = 0, maxBottom = 0;

    foreach(Monster *monster, m_monsters)
    {
        int left, top, right, bottom;
        if (monster->isDead()) continue;
        if(monster->collidesWith(player))
        {
            player->loseALife();
            monster->getGeometry(left, top, right, bottom);
            if (left < minLeft) minLeft = left;
            if (top < minTop) minTop = top;
            if (right > maxRight) maxRight = right;
            if (bottom > maxBottom) maxBottom = bottom;
            cancelMove = true;
        }
    }

    if (cancelMove)
        player->truncMove(minLeft, minTop, maxRight, maxBottom);
    return cancelMove;
}


bool Level::playerCollidesWithRock(Player *player)
{
    QList<MovingUnit*> collidesRock;

    foreach(Rock *rock, m_rocks)
    {
        if (rock->isDead()) continue;
        if(player->collidesWith(rock))
            collidesRock.append(rock);
    }

    if (collidesRock.isEmpty()) return false;

    if (player->getDirection() == MovingUnit::Up ||
        player->getDirection() == MovingUnit::Down)
    {
        movingUnitStoppedByMovingUnits(player, collidesRock);
        return true;
    }

    //on fait avancer les rock
    int minLeft = m_map->getWidth(), minTop = m_map->getHeight(), maxRight = 0, maxBottom = 0;
    bool rockCollided = false;

    foreach(MovingUnit *rock, collidesRock)
    {
        int rockRealSpeed = rock->getRealSpeed();
        rock->setRealSpeed(player->getRealSpeed());
        rock->moveTo(player->getDirection());
        rockCollided = rockCollides((Rock*)rock);
        rock->setRealSpeed(rockRealSpeed);
        
        int left, top, right, bottom;
        rock->getGeometry(left, top, right, bottom);
        if (left < minLeft) minLeft = left;
        if (top < minTop) minTop = top;
        if (right > maxRight) maxRight = right;
        if (bottom > maxBottom) maxBottom = bottom;
    }

    //si il y en a des stopé on appelle movingUnitStoppedByMovingUnits
    player->truncMove(minLeft, minTop, maxRight, maxBottom);

    if(! rockCollided)
    {
        int left, top, right, bottom;
        player->getGeometry(left, top, right, bottom);
        foreach(MovingUnit *rock, collidesRock)
            rock->truncCancelMove(left, top, right, bottom);
    }

    return true;
}

bool Level::movingUnitCollidesWithMap(MovingUnit *movingUnit)
{
    Unit *u;
    bool cancelMove = false;
    int left, top, right, bottom;
    QRect movingUnitRect = movingUnit->getGeometry();

    if(movingUnitRect.top() < 0 ||
       movingUnitRect.left() < 0 ||
       movingUnitRect.bottom() > (m_map->getHeight()) ||
       movingUnitRect.right() > (m_map->getWidth()))
    {
       movingUnit->truncMove(m_map->getWidth() - 1, m_map->getHeight() - 1, 0, 0);
       return true;
    }

    foreach(MapItem *item, m_map->getMapItemsContaining(movingUnit))
    {
        for(int k = 0; k < item->getNbUnits(); k++)
        {
            u = item->getUnitAt(k);
            if(!u->collidesWith(movingUnit)) break;
            if(item->getUnitAt(k)->getType() == Unit::Block)
            {
                u->getGeometry(left, top, right, bottom);
                cancelMove = true;
            }
        }
    }

    if(cancelMove)
        movingUnit->truncMove(left, top, right, bottom);
    return cancelMove;
}

void Level::movingUnitStoppedByMovingUnits(MovingUnit *movingUnit, QList<MovingUnit*> &movingUnits)
{
    int minLeft = m_map->getWidth(), minTop = m_map->getHeight(), maxRight = 0, maxBottom = 0;

    if (movingUnits.isEmpty()) return;

    foreach(MovingUnit *otherUnit, movingUnits)
    {
        int left, top, right, bottom;
        otherUnit->getGeometry(left, top, right, bottom);
        if (left < minLeft) minLeft = left;
        if (top < minTop) minTop = top;
        if (right > maxRight) maxRight = right;
        if (bottom > maxBottom) maxBottom = bottom;
    }

    movingUnit->truncMove(minLeft, minTop, maxRight, maxBottom);
}

bool Level::monsterCollidesWithPlayer(Monster *monster)
{
    int left, top, right, bottom;
    if( m_player->collidesWith(monster) )
    {
        m_player->getGeometry(left, top, right, bottom);
        monster->truncMove(left, top, right, bottom);
        m_player->loseALife();
        return true;
    }
    return false;
}

bool Level::monsterCollidesWithRock(Monster *monster)
{
    /*
    bool cancelMove = false;
    int left, top, right, bottom;

    foreach(Rock *rock, m_rocks)
    {
        if (rock->isDead()) continue;
        if(monster->collidesWith(rock))
        {
            rock->getGeometry(left, top, right, bottom);
            cancelMove = true;
        }
    }

    if (cancelMove)
        monster->truncMove(left, top, right, bottom);
    return cancelMove;
    */
    QList<MovingUnit*> collidesRock;

    foreach(Rock *rock, m_rocks)
    {
        if(rock->isDead()) continue;
        if(monster->collidesWith(rock))
            collidesRock.append(rock);
    }

    if (collidesRock.isEmpty()) return false;

    movingUnitStoppedByMovingUnits(monster, collidesRock);
    return true;
}

void Level::monsterCollides(Monster *monster)
{
    movingUnitCollidesWithMap(monster);
    monsterCollidesWithPlayer(monster);
    monsterCollidesWithRock(monster);
}

bool Level::rockPushingCollidesWithMovingUnits(Rock *rock)
{
    bool cancelMove = false;
    int left, top, right, bottom;

    foreach(Monster *monster, m_monsters)
    {
        if (monster->isDead()) continue;
        if( rock->collidesWith(monster) ) {
            cancelMove = true;
            monster->getGeometry(left, top, right, bottom);
        }
    }

    if (cancelMove)
        rock->truncMove(left, top, right, bottom);

    foreach(Rock *otherRock, m_rocks)
    {
        if (otherRock->isDead()) continue;
        if (otherRock == rock) continue;
        if( rock->collidesWith(otherRock) ) {
            cancelMove = true;
            otherRock->getGeometry(left, top, right, bottom);
        }
    }

    if (cancelMove)
        rock->truncMove(left, top, right, bottom);
    return cancelMove;
}

bool Level::rockFallingCollidesWithMovingUnits(Rock *rock)
{
    bool cancelMove = false;
    int left, top, right, bottom;

    if( rock->collidesWith(m_player) ) {
        m_player->loseALife();
        m_player->getGeometry(left, top, right, bottom);
        rock->truncMove(left, top, right, bottom);
    }

    foreach(Monster *monster, m_monsters)
    {
        if (monster->isDead()) continue;
        if( rock->collidesWith(monster) ) {

            monster->loseALife();
            cancelMove = true;
            monster->getGeometry(left, top, right, bottom);
        }
    }

    foreach(Rock *otherRock, m_rocks)
    {
        if (otherRock->isDead()) continue;
        if (otherRock == rock) continue;
        if( rock->collidesWith(otherRock) ) {
            cancelMove = true;
            otherRock->getGeometry(left, top, right, bottom);
        }
    }

    if (cancelMove)
        rock->truncMove(left, top, right, bottom);
    return cancelMove;
}

bool Level::rockCollides(Rock *rock)
{
    movingUnitCollidesWithMap(rock);

    if (rock->getDirection() != MovingUnit::Down)
    {
        return rockPushingCollidesWithMovingUnits(rock);
    }
    else
    {
        if (rock->getGeometry().bottom() == m_map->getHeight() -2)
        {
            rock->loseALife();
            return true;
        }
        return rockFallingCollidesWithMovingUnits(rock);
    }
}

void Level::advance()
{
    foreach(Rock *rock, m_rocks)
    {
        if (rock->isDead()) continue;
        rock->advance();
        rockCollides(rock);
    }

    m_countMonsters = 0;
    foreach(Monster *monster, m_monsters)
    {
        if (monster->isDead()) continue;
        monster->advance(m_player);
        monsterCollides(monster);
        m_countMonsters ++;
    }

    m_player->advance();
}



void Level::serializeDebug(QDebug &dbg)
{
    dbg << "Level(";
    dbg << m_map;
    dbg << m_player;


    dbg << "monsters" <<(qint32)m_monsters.count();
    foreach(Monster *monster, m_monsters)
        dbg << monster;

    dbg << "rocks" << (qint32)m_rocks.count();
    foreach(Rock *rock, m_rocks)
        dbg << rock;

    dbg << ")";
}

void Level::serializeOut(QDataStream &out)
{
    out << (qint32)m_map->getRows();
    out << (qint32)m_map->getColumns();
    out << m_map;

    out << m_player;

    out << (qint32)m_monsters.count();
    foreach(Monster *monster, m_monsters)
        out << monster;

    out << (qint32)m_rocks.count();
    foreach(Rock *rock, m_rocks)
        out << rock;
}

void Level::serializeIn(QDataStream &in)
{
    int rows, columns;
    int monstersCount, rocksCount;
    Monster *monster; Rock *rock;

    in >> rows;
    in >> columns;
    m_map = new Map(rows, columns, 0 , 0);
    in >> m_map;

    m_player = new Player();
    in >> m_player;

    in >> monstersCount;
    for(int i = 0; i < monstersCount; i++)
    {
        monster = new Monster();
        in >> monster;
        addMonster(monster);
    }

    in >> rocksCount;
    for(int i = 0; i < rocksCount; i++)
    {
        rock = new Rock();
        in >> rock;
        addRock(rock);
    }

    m_countFruits = m_map->countFruits();
    m_countMonsters = monstersCount;
}

QDataStream & operator << (QDataStream &out, Level* level)
{
    level->serializeOut(out);
    return out;
}

QDataStream & operator >> (QDataStream &in, Level* level)
{
    level->serializeIn(in);
    return in;
}

QDebug operator<<(QDebug dbg, Level* level)
{
    level->serializeDebug(dbg);
    return dbg;
}



